﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Microsoft.Phone.Reactive;

using Codeplex.OAuth;

namespace Tequila.Phone.Sharing.Twitpic
{
    /// <summary>
    /// Tiny Twitpic client implementation.
    /// </summary>
    public class TwitpicClient
    {
        private readonly TwitpicAuthorizationSession _session;
        private readonly Action<TwitpicResponse> _callback;

        /// <summary>
        /// Initializes new instance of class.
        /// </summary>
        /// <param name="session"></param>
        /// <param name="callback"></param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="ShareServiceException"></exception>
        public TwitpicClient(TwitpicAuthorizationSession session, Action<TwitpicResponse> callback)
        {
            if (session == null)
            {
                throw new ArgumentException("twitPicAuthorizationSession");
            }
            if (callback == null)
            {
                throw new ArgumentException("callback");
            }
            if (!session.IsValid)
            {
                throw new ShareServiceException(ShareServiceErrorType.InvalidSession);
            }

            _session = session;
            _callback = callback;
        }

        /// <summary>
        /// Begins async image (presented as <see cref="Stream">stream</see>) upload.
        /// </summary>
        /// <param name="imageStream"></param>
        /// <param name="message"></param>
        /// <param name="fileName"></param>
        /// <exception cref="ArgumentException"></exception>
        public void BeginUpload(Stream imageStream, string message, string fileName)
        {
            if (imageStream == null)
            {
                throw new ArgumentException("imageStream");
            }

            imageStream.Position = 0;
            var imageBytes = new byte[imageStream.Length];
            imageStream.Read(imageBytes, 0, (int) (imageStream.Length));

            BeginUpload(imageBytes, message, fileName);
        }

        /// <summary>
        /// Begins async image (presented as byte[] array) upload.
        /// </summary>
        /// <param name="imageBytes"></param>
        /// <param name="message"></param>
        /// <param name="fileName"></param>
        public void BeginUpload(byte[] imageBytes, string message, string fileName)
        {
            var accessToken = new AccessToken(_session.AccessTokenKey, _session.AccessTokenSecret);
            var client = new InnerClient(_session.ConsumerKey, _session.ConsumerSecret, accessToken, _session.ApiKey);

            client.UploadPicture(fileName, message, imageBytes)
                .Catch((WebException ex) =>
                           {
                               //TODO:
                               // var tmp = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd();
                               return Observable.Empty<string>();
                           })
                .Subscribe(
                    ProcessSuccess,
                    ProcessFailure);
        }

        private void ProcessFailure(Exception exception)
        {
            var response = new TwitpicResponse(exception.Message);
            _callback(response);
        }

        private void ProcessSuccess(string result)
        {
            var response = TwitpicResponseUtility.ParseResponse(result);
            _callback(response);
        }


        #region TwitpicClient

        private class InnerClient : OAuthBase
        {
            private readonly string _apiKey;

            private readonly AccessToken _accessToken;

            public InnerClient(
                string consumerKey
                , string consumerSecret
                , AccessToken accessToken
                , string apiKey)
                : base(consumerKey, consumerSecret)
            {
                _accessToken = accessToken;

                if (string.IsNullOrEmpty(apiKey))
                {
                    throw new ArgumentNullException("apiKey");
                }

                _apiKey = apiKey;
            }

            private WebRequest CreateRequest(string url)
            {
                const string serviceProvider = "https://api.twitter.com/1/account/verify_credentials.json";
                const string realm = "http://api.twitter.com/";

                var req = WebRequest.Create(url);

                // generate oauth signature and parameters
                var parameters = ConstructBasicParameters(serviceProvider, MethodType.Get, _accessToken);
                // make auth header string
                var authHeader = BuildAuthorizationHeader(new[] {new Parameter("Realm", realm)}.Concat(parameters));

                // set authenticate headers
                req.Headers["X-Verify-Credentials-Authorization"] = authHeader;
                req.Headers["X-Auth-Service-Provider"] = serviceProvider;

                return req;
            }

            public IObservable<string> UploadPicture(string filename, string message, byte[] file)
            {
                var req = CreateRequest("http://api.twitpic.com/2/upload.xml"); // choose xml or json
                req.Method = "POST";

                var boundaryKey = Guid.NewGuid().ToString();
                var boundary = "--" + boundaryKey;
                req.ContentType = "multipart/form-data; boundary=" + boundaryKey;

                return Observable.Defer(() =>
                                        Observable.FromAsyncPattern<Stream>(req.BeginGetRequestStream,
                                                                            req.EndGetRequestStream)())
                    .Do(stream =>
                            {
                                using (stream)
                                {
                                    var sw = new StreamWriter(stream, new UTF8Encoding(false));
                                    sw.WriteLine(boundary);
                                    sw.WriteLine("Content-Disposition: form-data; name=\"key\"");
                                    sw.WriteLine();
                                    sw.WriteLine(_apiKey);

                                    sw.WriteLine(boundary);
                                    sw.WriteLine("Content-Disposition: form-data; name=\"message\"");
                                    sw.WriteLine();
                                    sw.WriteLine(message);

                                    sw.WriteLine(boundary);
                                    sw.WriteLine("Content-Disposition: form-data; name=\"media\"; filename=\"" +
                                                 filename + "\"");
                                    sw.WriteLine("Content-Type: application/octet-stream");
                                    sw.WriteLine("Content-Transfer-Encoding: binary");
                                    sw.WriteLine();
                                    sw.Flush();

                                    stream.Write(file, 0, file.Length);
                                    stream.Flush();

                                    sw.WriteLine();
                                    sw.WriteLine("--" + boundaryKey + "--");
                                    sw.Flush();
                                }
                            })
                    .SelectMany(
                        _ => Observable.FromAsyncPattern<WebResponse>(req.BeginGetResponse, req.EndGetResponse)())
                    .Select(res =>
                                {
                                    using (res)
                                    {
                                        using (var stream = res.GetResponseStream())
                                        {
                                            var sr = new StreamReader(stream, Encoding.UTF8);
                                            return sr.ReadToEnd();
                                        }
                                    }
                                });
            }
        }

        #endregion //TwitpicClient
    }
}
